import Roommgr from "./roommgr";
import UserMgr from "./usermgr";
import { game_event_map } from "../configs_win";
import MjUtils from "./mjutils";

import Db from "../utils/db";
import Crypto from "../utils/crypto";




function logErr(funName){
    let pathFun = 'game_server->gamemgr_zxdd.ts->';
    return (errMsg) => {
        console.log(pathFun + funName + '()->错误信息:' + errMsg);
    };
}


function debugToTextArr(arr: number[]){
    let tempArr = [];
    for(let i = 0; i < arr.length; i++){
        tempArr[i] = arr[i];
    }

    tempArr.sort((a, b) => {
        return a - b;
    });

    for(let i = 0; i < tempArr.length; i++){
        let n = '';
        let t = tempArr[i] % 9 + 1;
        if(tempArr[i] < 9){
            n = '筒';
        }
        else if (tempArr[i] < 18){
            n = '条';
        }
        else{
            n = '万'; 
        }
        tempArr[i] = t + n;
    }
    return tempArr;
}


function debugToText(mjId){
    let n = '';
    let x = mjId % 9 + 1;
    if (mjId < 9) {
        n = '筒';
    }
    else if (mjId < 18) {
        n = '条';
    }
    else {
        n = '万';
    }
    return x + n;
}

enum ACTION{
    CHUPAI = 1,
    MOPAI,
    PENG,
    GANG,
    HU,
    ZIMO,
}

export default class GameMgr_xzdd {

    static games: games = {};

    static dissolvingList = [];

    static gameSeatsOfUsers: gameSeatsOfUsers = {};
    static setReady(userId){
        const log = logErr('setReady');

        const roomId = Roommgr.getUserRoom(userId);
        if(roomId == null){
            log('roomId == null');
            return;
        }

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if (roomInfo == null) {
            log('roomInfo == null');
            return;
        }


        Roommgr.setReady(userId, true);
        
        const game = this.games[roomId];
        if(game == null){
            if(roomInfo.seats.length == 4){
                for(let i = 0; i < roomInfo.seats.length; i++){
                    const s = roomInfo.seats[i];
                    if(s.ready == false || UserMgr.isOnline(s.userId) == false){
                        log('s.ready == false || UserMgr.isOnline(s.userId) == false');
                        return;
                    }
                }
                // 如果4人都到齐了，并且都准备了，并且没有掉线的就开局
                this.begin(roomId);
            }
        }
        else{
            const numOfMJ = game.mahjongs.length - game.currentIndex;
            
            const data: game = {
                state: game.state,
                numOfMJ: numOfMJ,
                button: game.button,
                turn: game.turn,
                chuPai: game.chuPai,
                huanPaiMethod: game.huanPaiMethod,
            };

            data.seats = [];
            let seatData = null;
            for(let i = 0; i < 4; i++){
                const sd = game.gameSeats[i];

                const s: gameSeat = {
                    userId: sd.userId,
                    folds: sd.folds,
                    anGangs: sd.anGangs,
                    dianGangs: sd.dianGangs,
                    wanGangs: sd.wanGangs,
                    pengs: sd.pengs,
                    que: sd.que,
                    hued: sd.hued,
                    isZiMo: sd.isZiMo,
                };

                if(sd.userId == userId){
                    s.holds = sd.holds;
                    s.huanPais = sd.huanPais;
                    seatData = sd;
                }
                else{
                    s.huanPais = sd.huanPais ? [] : null;
                }
                data.seats.push(s);
            }
            // 同步整个信息给客户端
            UserMgr.sendMsg(userId, game_event_map.GAME_SYNC_PUSH, data);
            this.sendOperations(game, seatData, game.chuPai);
        }
    }


    /**执行操作 */
    static sendOperations(game: game, seatData: gameSeat, pai){
        if(this.hasOperations(seatData)){
            if(pai == -1){
                pai = seatData.holds[seatData.holds.length - 1];
            }
            const data: any = {
                pai: pai,
                hu: seatData.canHu,
                peng: seatData.canPeng,
                gang: seatData.canGang,
                gangPai: seatData.gangPai,
            };

            // 如果可以有操作，则进行操作
            UserMgr.sendMsg(seatData.userId, game_event_map.GAME_ACTION_PUSH, data);
            data.si = seatData.seatIndex;
        }
        else{
            UserMgr.sendMsg(seatData.userId, game_event_map.GAME_ACTION_PUSH);
        }
    }


    /**检查是否有需要执行的特殊操作 */
    static hasOperations(seatData: gameSeat){
        return (seatData.canGang || seatData.canPeng || seatData.canHu) ? true : false;
    }

    /**
     * 开始
     */
    static begin(roomId){
        const log = logErr('begin');

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if(roomInfo == null){
            log('roomInfo == null');
            return;
        }

        const seats = roomInfo.seats;
        const game: game = {
            conf: roomInfo.conf,
            roomInfo: roomInfo,
            gameIndex: roomInfo.numOfGames,
            button: roomInfo.nextButton,
            mahjongs: new Array(108),
            currentIndex: 0,
            gameSeats: [],
            numOfQue: 0,
            turn: 0,
            chuPai: -1,
            state: 'idle',
            firstHuPai: -1,
            yiPaoDuoXiang: -1,
            fangPaoShuMu: -1,
            actionList: [],
            huPaiList: [],
            chuPaiCnt: 0,
        };

        roomInfo.numOfGames++;

        for(let i = 0; i <4; i++){
            const data:gameSeat = game.gameSeats[i] = {};
            data.game = game;
            data.seatIndex = i;
            data.userId = seats[i].userId;
            data.holds = [];
            data.folds = [];
            data.anGangs = [];
            data.dianGangs = [];
            data.wanGangs = [];
            data.pengs = [];
            data.que = -1;
            data.huanPais = null;
            data.countMap = {};
            data.tingMap = {};
            data.pattern = '';
            data.canGang = false;
            data.gangPai = [];
            data.canPeng = false;
            data.canHu = false;
            data.canChuPai = false;
            // 如果guoHuFan >= 0 表示处于过胡的状态
            // 如果过胡状态，那么只能胡大于过胡番数的牌
            data.guoHuFan = -1;
            data.hued = false;
            data.isZiMo = false;
            data.actions = [];
            data.fan = 0;
            data.score = 0;
            data.lastFangGangSeat = -1;
            // 统计信息
            data.numZiMo = 0;
            data.numJiePao = 0;
            data.numDianPao = 0;
            data.numAnGang = 0;
            data.numMingGang = 0;
            data.numChaJiao = 0;

            this.gameSeatsOfUsers[data.userId] = data;
        }

        this.games[roomId] = game;
        this.shuffle(game);
        this.deal(game);

        const numOfMJ = game.mahjongs.length - game.currentIndex;
        const huanSanZhang = roomInfo.conf.hsz;

        for(let i = 0; i < seats.length; i++){
            const s = seats[i];
            UserMgr.sendMsg(s.userId, game_event_map.GAME_HOLDS_PUSH, game.gameSeats[i].holds);
            UserMgr.sendMsg(s.userId, game_event_map.MJ_COUNT_PUSH, numOfMJ);
            UserMgr.sendMsg(s.userId, game_event_map.GAME_NUM_PUSH, roomInfo.numOfGames)
            UserMgr.sendMsg(s.userId, game_event_map.GAME_BEGIN_PUSH, game.button);

            if(huanSanZhang){
                game.state = 'huanpai';
                UserMgr.sendMsg(s.userId, game_event_map.GAME_HUANPAI_PUSH);
            }
            else{
                game.state = 'dingque';
                UserMgr.sendMsg(s.userId, game_event_map.GAME_DINGQUE_PUSH);
            }
        }
    }


    /**
     * 洗牌
     */
    static shuffle(game: game){
        const mahjongs = game.mahjongs;
        let index = 0;

        const initNum = (min, max) => {
            for(; min < max; min++){
                for(let i = 0; i < 4; i++){   
                    mahjongs[index] = min;
                    index++;
                }
            }
        };

        // 0 - 8 筒子
        initNum(0, 9);
        // 9 - 17 条子
        initNum(9, 18);
        // 18 - 26 万
        initNum(18, 27);

        for (let i = 0; i < mahjongs.length; ++i) {
            const lastIndex = mahjongs.length - 1 - i;
            const index = Math.floor(Math.random() * lastIndex);
            const t = mahjongs[index];
            mahjongs[index] = mahjongs[lastIndex];
            mahjongs[lastIndex] = t;
        }
    }


    /**
     * 发牌
     */
    static deal(game: game){

        game.currentIndex = 0;

        // 每人13张 一共 13*4 ＝ 52张 庄家多一张 53张
        let seatIndex = game.button;
        for(let i = 0; i < 52; i++){
            if(game.gameSeats[seatIndex].holds == null){
                game.gameSeats[seatIndex].holds = [];
            }
            this.moPai(game, seatIndex);
            seatIndex++;
            seatIndex %= 4;
        }

        // 庄家多模最后一张牌
        this.moPai(game, game.button);
        // 当前轮设置为庄家
        game.turn = game.button;
    }


    /**
     * 
     */
    static moPai(game: game, seatIndex){
        const log = logErr('moPai');
        if(game.currentIndex == game.mahjongs.length){
            log('game.currentIndex == game.mahjongs.length -> 牌摸没了');
            return -1;
        }

        const data = game.gameSeats[seatIndex];
        const mahjongs = data.holds;
        const pai = game.mahjongs[game.currentIndex];
        mahjongs.push(pai);

        let c = data.countMap[pai];
        if(c == null){
            c = 0;
        }
        data.countMap[pai] = c + 1;
        game.currentIndex++;
        console.log('下一家摸的牌是' + debugToText(pai));
        return pai;
    }


    static huanSanZhang(userId, p1, p2, p3){
        const log = logErr('huanSanZhang');

        const seatData = this.gameSeatsOfUsers[userId];
        if(seatData == null){
            log('seatData == null');
            return;
        }

        const game = seatData.game;
        if(game.state != 'huanpai'){
            log('(game.state == \'huanpai\')当游戏状态不是huanpai的时候不能换三张');
            return;
        }

        if(seatData.huanPais != null){
            log('(seatData.huanPais != null)玩家已经做了这个动作');
            return;
        }

        if (seatData.countMap[p1] == null || seatData.countMap[p1] == 0){
            log('seatData.countMap[p1] == null || seatData.countMap[p1] == 0');
            return;
        }

        seatData.countMap[p1]--;
        if(seatData.countMap[p2] == null || seatData.countMap[p2] == 0){
            seatData.countMap[p1]++;
            log('seatData.countMap[p2] == null || seatData.countMap[p2] == 0');
            return;
        }
        
        seatData.countMap[p2]--;
        if (seatData.countMap[p3] == null || seatData.countMap[p3] == 0) {
            seatData.countMap[p1]++;
            seatData.countMap[p2]++;
            log('seatData.countMap[p3] == null || seatData.countMap[p3] == 0');
            return;
        }

        seatData.countMap[p1]++;
        seatData.countMap[p2]++;
        seatData.huanPais = [p1, p2, p3];
        seatData.huanPais.forEach((p) => {
            const idx = seatData.holds.indexOf(p);
            seatData.holds.splice(idx, 1);
            seatData.countMap[p]--;
        });

        UserMgr.sendMsg(seatData.userId, game_event_map.GAME_HOLDS_PUSH, seatData.holds,);

        game.gameSeats.forEach((sd) => {
            if(sd == seatData){
                const rd = {
                    si: seatData.userId,
                    huanPais: seatData.huanPais,
                };
                UserMgr.sendMsg(sd.userId, game_event_map.HUANPAI_NOTIFY, rd);
            }
            else{
                const rd = {
                    si: seatData.userId,
                    huanPais: [],
                };
                UserMgr.sendMsg(sd.userId, game_event_map.HUANPAI_NOTIFY, rd);
            }
        });

        // 如果还有未换牌的玩家，则继承等待
        for(let i = 0; i < game.gameSeats.length; i++){
            if(game.gameSeats[i].huanPais == null){
                log('如果还有未换牌的玩家，则继承等待');
                return;
            }
        }


        // 换牌函数
        const fn = (s1: gameSeat, huanjin: number[]) => {
            huanjin.forEach((p) => {
                s1.holds.push(p);
                if(s1.countMap[p] == null){
                    s1.countMap[p] = 0;
                }
                s1.countMap[p]++;
            });
        };


        // 开始换牌
        const f = Math.random();
        const s = game.gameSeats;
        let huanPaiMethod = 0;
        // 对家换牌
        if(f < 0.33){
            fn(s[0], s[2].huanPais);
            fn(s[1], s[3].huanPais);
            fn(s[2], s[0].huanPais);
            fn(s[3], s[1].huanPais);
            huanPaiMethod = 0;
        }
        // 换下家的牌
        else if(f < 0.66){
            fn(s[0], s[1].huanPais);
            fn(s[1], s[2].huanPais);
            fn(s[2], s[3].huanPais);
            fn(s[3], s[0].huanPais);
            huanPaiMethod = 1;
        }
        // 换上家的牌
        else{
            fn(s[0], s[3].huanPais);
            fn(s[1], s[0].huanPais);
            fn(s[2], s[1].huanPais);
            fn(s[3], s[2].huanPais);
            huanPaiMethod = 2;
        }

        const rd = {method: huanPaiMethod};
        game.huanPaiMethod = huanPaiMethod;
        game.state = 'dingque';
        s.forEach((item, i) => {
            const userId = item.userId;
            UserMgr.sendMsg(userId, game_event_map.GAME_HUANPAI_OVER_PUSH, rd);
            UserMgr.sendMsg(userId, game_event_map.GAME_HOLDS_PUSH, item.holds);
            UserMgr.sendMsg(userId, game_event_map.GAME_DINGQUE_PUSH);
            console.log(`玩家${item.userId}：换完牌后的手牌是${debugToTextArr(item.holds)}`);
        });
    }



    static dingQue(userId, type){
        const log = logErr('dingQue');

        const seatData = this.gameSeatsOfUsers[userId];
        if (seatData == null) {
            log('seatData == null');
            return;
        }

        const game = seatData.game;
        if (game.state == 'huanpai') {
            log('(game.state == \'huanpai\')当游戏状态是huanpai的时候不能换三张');
            return;
        }

        if(seatData.que < 0){
            game.numOfQue++;
        }
        seatData.que = type;

        // 检查玩家可以做的动作，如果4个人都定缺了，通知庄家出牌
        if(game.numOfQue == 4){
            this.construct_game_base_info(game);
            const arr = [1, 1, 1, 1];
            game.gameSeats.forEach((gameSeat, i) => {
                arr[i] = gameSeat.que;
            });
            UserMgr.broacastInRoom(game_event_map.GAME_DINGQUE_FINISH_PUSH, arr, seatData.userId, true);
            UserMgr.broacastInRoom(game_event_map.GAME_PLAYING_PUSH, null, seatData.userId, true);

            // 进行听牌检查
            game.gameSeats.forEach((gs) => {
                let duoyu = -1;
                if(gs.holds.length == 14){
                    duoyu = gs.holds.pop();
                    gs.countMap[duoyu]--;
                }
                this.checkCanTingPai(game, gs);
                if(duoyu >= 0){
                    gs.holds.push(duoyu);
                    gs.countMap[duoyu]++;
                }
            });

            const turnSeat = game.gameSeats[game.turn];
            game.state = 'playing';
            // 通知玩家出牌方
            turnSeat.canChuPai = true;
            UserMgr.broacastInRoom(game_event_map.GAME_CHUPAI_PUSH, turnSeat.userId, turnSeat.userId, true);
            //检查是否可暗杠
            this.checkCanAnGang(game, turnSeat);
            // 用最后一张牌来检查是否可以胡牌
            this.checkCanHu(game, turnSeat, turnSeat.holds[turnSeat.holds.length - 1]);
            // 通知前端
            this.sendOperations(game, turnSeat, game.chuPai);
        }
        else{
            UserMgr.broacastInRoom(game_event_map.GAME_DINGQUE_NOTIFY_PUSH, seatData.userId, seatData.userId, true);
        }

    }


    static checkCanHu(game: game, seatData: gameSeat, targetPai){
        game.lastHuPaiSeat = -1;
        if(this.getMJType(targetPai) == seatData.que){
            return;
        }
        seatData.canHu = false;
        for(let k in seatData.tingMap){
            if(targetPai == k){
                seatData.canHu = true;
            }
        }
    }



    /**
     *检查是否可暗杠
     */
    static checkCanAnGang(game: game, seatData: gameSeat){
        // 如果没有牌了，则不能再杠
        if(game.mahjongs.length <= game.currentIndex){
            return;
        }

        for(let k in seatData.countMap){
            const pai = parseInt(k);
            if(this.getMJType(pai) != seatData.que){
                const c = seatData.countMap[k];
                if(c != null && c == 4){
                    seatData.canGang = true;
                    seatData.gangPai.push(pai);
                }
            }
        }
    }



    /**
     * 检查听牌
     */
    static checkCanTingPai(game: game, seatData: gameSeat){
        seatData.tingMap = {};

        // 检查手上的牌是不是已打缺，如果未打缺，则不进行判定
        for(let i = 0; i < seatData.holds.length; i++){
            if (this.getMJType(seatData.holds[i]) == seatData.que) {
                return;
            }
        }

        // 检查是否是七对，前提是没有碰，也没有杠，即手上拥有13张牌
        if(seatData.holds.length == 13){
            // 有5对牌
            let hu = false;
            let danPai:any = -1;
            let pairCount = 0;
            for(let k in seatData.countMap){
                const c = seatData.countMap[k];
                if(c == 2 || c == 3){
                    pairCount++;
                }
                else if(c ==  4){
                    pairCount += 2;
                }

                if(c == 1 || c == 3){
                    // 如果已经有单牌了，标识不止一张单牌，并没有下叫，直接闪
                    if(danPai >= 0){
                        break;
                    }
                    danPai = k;
                }
            }

            // 检查是不是6对，并且单牌是不是目标牌
            if(pairCount == 6){
                // 七对智能和一张牌，就是手上的那张单牌
                // 七对的番数= 2番+N个4个牌，即七龙对
                seatData.tingMap[danPai] = {
                    fan: 2,
                    pattern: '7pairs',
                };
            }
        }

        //检查是否是对对胡  由于四川麻将没有吃，所以只需要检查手上的牌
        //对对胡叫牌有两种情况
        //1、N坎 + 1张单牌
        //2、N-1坎 + 两对牌
        let singleCount = 0;
        let colCount = 0;
        let pairCount = 0;
        let arr = [];
        for(let k in seatData.countMap){
            const c= seatData.countMap[k];
            if(c == 1){
                singleCount++;
                arr.push(k);
            }
            else if(c == 2){
                pairCount++;
                arr.push(k);
            }
            else if(c == 3){
                colCount++;
            }
            else if(c == 4){
                // 手上有4个一样的牌，在四川麻将中是胡不了对对胡的，随便加点东西
                singleCount++;
                pairCount += 2;
            }
        }

        if((pairCount == 2 && singleCount == 0) || (pairCount == 0 && singleCount == 1)){
            arr.forEach((p) => {
                // 对对胡1番
                if(seatData.tingMap[p] == null){
                    seatData.tingMap[p] = {
                        fan: 1,
                        pattern:'duidui',
                    };
                }
            });
        }


        // 检查是不是平胡
        if(seatData.que != 0){
            MjUtils.checkTingPai(seatData, 0, 9);
        }

        if(seatData.que != 1){
            MjUtils.checkTingPai(seatData, 9, 18);
        }

        if(seatData.que != 2){
            MjUtils.checkTingPai(seatData, 18, 27);
        }
    }

    static construct_game_base_info(game: game){
        const baseInfo: baseInfoJson = {
            type: game.conf.type,
            button: game.button,
            index: game.gameIndex,
            mahjongs: game.mahjongs,
            game_seats: []
        };

        for(let i = 0; i < 4; i++){
            baseInfo.game_seats[i] = game.gameSeats[i].holds;
        }
        game.baseInfoJson = <baseInfoJson>JSON.stringify(baseInfo);
    }

    /**
     * 获取牌的类型 0-筒 1-条 2-万
     * @param id 
     */
    static getMJType(id) {
        if (id >= 0 && id < 9) {
            //筒
            return 0;
        }
        else if (id >= 9 && id < 18) {
            //条
            return 1;
        }
        else if (id >= 18 && id < 27) {
            //万
            return 2;
        }
    }


    /**
     * 检查是否可以碰
     * @param game 
     * @param seatData 
     * @param targetPai 
     */
    static checkCanPeng(game: game, seatData: gameSeat, targetPai){
        if(this.getMJType(targetPai) == seatData.que){
            return;
        }

        const count = seatData.countMap[targetPai];
        if(count != null && count >= 2){
            seatData.canPeng = true;
        }
    }


    /**
     * 检查是否可以点杠
     * @param game 
     * @param seatData 
     * @param targetPai 
     */
    static checkCanDianGang(game: game, seatData: gameSeat, targetPai){
        // 检查玩家手上的牌，如果没有牌了则不能再杠
        if(game.mahjongs.length <= game.currentIndex){
            return;
        }

        if(this.getMJType(targetPai) == seatData.que){
            return;
        }

        const count = seatData.countMap[targetPai];
        if(count != null && count >= 3){
            seatData.canGang = true;
            seatData.gangPai.push(targetPai);
        }
    }

    /**
     * 更新回合编号或找出下一个没有胡牌的回合编号
     * @param game 
     * @param nextSeat 
     */
    static moveToNextUser(game: game, nextSeat?){
        game.fangPaoShuMu = 0;
        if(nextSeat == null){
            while(true){
                game.turn++;
                game.turn %= 4;
                const turnSeat = game.gameSeats[game.turn];
                if(turnSeat.hued == false){
                    return;
                }
            }
        }
        else{
            game.turn = nextSeat;
        }
    }


    /**
     * 检查是否听牌
     * @param seatData 
     */
    static isTinged(seatData: gameSeat){
        for(let k in seatData.tingMap){
            return true;
        }
        return false;
    }


    /**
     * 检查是否需要查大叫  
     * 查大叫规则：有两家或以上玩家没有胡牌，并且他们有人没有下叫
     * 下叫规则： 玩家还差一张牌就能胡牌（应该就是听牌的意思吧）
     * @param game 
     */
    static needChaDaJiao(game: game){
        let numOfHued = 0;
        let numOfTinged = 0;
        let numOfUntinged = 0;

        for(let i = 0; i < game.gameSeats.length; i++){
            const ts = game.gameSeats[i];
            if(ts.hued){
                numOfHued++;
                numOfTinged++;
            }
            else if(this.isTinged(ts)){
                numOfTinged++;
            }
            else{
                numOfUntinged++;
            }
        }

        // 如果有3家胡牌了，不需要查叫
        if(numOfHued >= 3){
            return false;
        }

        // 如果没有人叫牌，也没有人胡牌，不需要查叫
        if(numOfTinged == 0){
            return false;
        }

        // 如果都叫牌了，也不需要查叫
        if(numOfUntinged == 0){
            return false;
        }

        return true;
    }

    /**
     * 记录玩家状态  
     * @param game 
     * @param seatData 
     * @param type 
     * @param target 
     */
    static recordUserAction(game: game, seatData: gameSeat, type, target?): actions{
        const d: actions = {
            type: type,
            targets: [],
        };

        if(target != null){
            if(typeof target == 'number'){
                d.targets.push(target);
            }
            else{
                d.targets = target;
            }
        }
        else{
            for(let i = 0; i < game.gameSeats.length; i++){
                const s = game.gameSeats[i];
                if(i != seatData.seatIndex && s.hued == false){
                    d.targets.push(i);
                }
            }
        }

        seatData.actions.push(d);
        return d;
    }

    /**
     * 返回没有胡牌和没有听牌的玩家的座位编号的数组
     * @param game 
     */
    static findUnTingedPlayers(game: game){
        const arr = [];
        for(let i = 0; i < game.gameSeats.length; i++){
            const ts = game.gameSeats[i];
            // 找出没有胡牌也没有听牌的
            if(!ts.hued && !this.isTinged(ts)){
                arr.push(i);
                this.recordUserAction(game, ts, 'beichadajiao', -1);
            }
        }
        return arr;
    }
    
    /**
     * 找出最大番
     * @param ts 
     */
    static findMaxFanTingPai(ts: gameSeat){
        let cur: tingMap = null;
        for(let k in ts.tingMap){
            const tingPai = ts.tingMap[k];
            if(cur == null || tingPai.fan > cur.fan){
                cur = tingPai;
            }
        }
        return cur;
    }

    static chaJiao(game: game){
        const arr = this.findUnTingedPlayers(game);
        for(let i = 0; i < game.gameSeats.length; i++){
            const ts = game.gameSeats[i];
            // 如果没有胡，但是听牌了，则未叫牌的人要给钱
            if(!ts.hued && this.isTinged(ts)){
                const cur = this.findMaxFanTingPai(ts);
                ts.fan = cur.fan;
                ts.pattern = cur.pattern;
                this.recordUserAction(game, ts, 'chadajiao', arr);
            }
        }
    }

    /**
     * 检查花色是否一致
     */
    static isSameType(type, arr: any[]){
        for(let i = 0; i < arr.length; i++){
            const t = this.getMJType(arr[i]);
            if(type != -1 && type != t){
                return false;
            }
            type = t;
        }
        return true;
    }


    /**
     * 检查是否是清一色
     * @param gameSeatData 
     */
    static isQingYiSe(gameSeatData: gameSeat){
        const type = this.getMJType(gameSeatData.holds[0]);

        // 检查手上的牌是否花色一致
        if(this.isSameType(type, gameSeatData.holds) == false){
            return false;
        }

        // 检查杠下的牌是否花色一致
        if(this.isSameType(type, gameSeatData.anGangs) == false){
            return false;
        }
        if(this.isSameType(type, gameSeatData.wanGangs) == false){
            return false;
        }
        if(this.isSameType(type, gameSeatData.dianGangs) == false){
            return false;
        }

        // 检查碰的牌是否花色一致
        if(this.isSameType(type, gameSeatData.pengs) == false){
            return false;
        }

        return true;
    }


    /**
     * 是否是将对
     * @param gameSeatData 
     */
    static isJiangDui(gameSeatData: gameSeat){
        const fn = (arr: any[]) => {
            for(let i = 0, l = arr.length; i < l; i++){
                let pai = arr[i];
                if (pai != 1 && pai != 4 && pai != 7 && pai != 9 && pai != 13 && pai != 16 && pai != 18 && pai != 21 && pai != 25){
                    return false;
                }
            }
            return true;
        };

        return fn(gameSeatData.pengs) && fn(gameSeatData.anGangs) && fn(gameSeatData.dianGangs) && fn(gameSeatData.wanGangs) && fn(gameSeatData.holds);
    }
    
    
    /**
     * 是否是中张
     * @param gameSeatData 
     */
    static isZhongZhang(gameSeatData: gameSeat){
        const fn = (arr: any[]) => {
            for(let i = 0, l = arr.length; i < l; i++){
                let pai = arr[i];
                if (pai != 0 && pai != 8 && pai != 9 && pai != 17 && pai != 18 && pai != 26){
                    return false;
                }
            }
            return true;
        };
    
        return fn(gameSeatData.pengs) && fn(gameSeatData.anGangs) && fn(gameSeatData.dianGangs) && fn(gameSeatData.wanGangs) && fn(gameSeatData.holds);

    }



    /**
     * 是否是门清(应该就是没有碰杠的牌就是门清)
     * @param gameSeatData 
     */
    static isMenQing(gameSeatData: gameSeat){
        return (gameSeatData.pengs.length + gameSeatData.anGangs.length + gameSeatData.wanGangs.length + gameSeatData.dianGangs.length) == 0;
    }


    static computeFanScore(game: game, fan){
        if(fan > game.conf.maxFan){
            fan = game.conf.maxFan;
        }
        return (1 << fan) * game.conf.baseScore;
    }


    static calculateResult(game: game, roomInfo: roomInfo){
        const isNeedChaDaJia = this.needChaDaJiao(game);
        if(isNeedChaDaJia){
            this.chaJiao(game);
        }

        const baseScore = game.conf.baseScore;
        let numOfHued = 0;
        for(let i = 0; i < game.gameSeats.length; i++){
            if(game.gameSeats[i].hued == true){
                numOfHued++;
            }
        }

        for(let i = 0; i < game.gameSeats.length; i++){
            const sd = game.gameSeats[i];

            // 统计杠的数目
            sd.numAnGang = sd.anGangs.length;
            sd.numMingGang = sd.wanGangs.length + sd.dianGangs.length;

            // 对所有胡牌的玩家进行统计
            if(this.isTinged(sd)){
                //统计自己的番子和分数
                //基础番(平胡0番，对对胡1番、七对2番) + 清一色2番 + 杠+1番
                //杠上花+1番，杠上炮+1番 抢杠胡+1番，金钩胡+1番，海底胡+1番
                let fan = sd.fan;
                if(this.isQingYiSe(sd)){
                    sd.qingYiSe = true;
                    fan += 2;
                }

                let numOfGames = sd.dianGangs.length + sd.wanGangs.length + sd.anGangs.length;
                for(let j = 0; i < sd.pengs.length; i++){
                    const pai = sd.pengs[j];
                    if(sd.countMap[pai] == 1){
                        numOfGames++;
                    }
                }

                for(let k in sd.countMap){
                    if(sd.countMap[k] == 4){
                        numOfGames++;
                    }
                }
                sd.numOfGen = numOfGames;

                // 金钩胡
                if(sd.holds.length == 1 || sd.holds.length == 2){
                    fan += 1;
                    sd.isJinGouHu = true;
                }
                // 海底胡
                if(sd.isHaiDiHu){
                    fan += 1;
                }

                // 天地胡
                if(game.conf.tianDiHu){
                    if(sd.isTianHu){
                        fan += 3;
                    }
                    else if(sd.isDiHu){
                        fan += 2;
                    }
                }

                // 将对
                let isJiangDui = false;
                if(game.conf.jiangDui){
                    if(sd.pattern == '7pairs'){
                        if(sd.numOfGen > 0){
                            sd.numOfGen -= 1;
                            sd.pattern = 'l7pairs';
                            isJiangDui = this.isJiangDui(sd);
                            if(isJiangDui){
                                sd.pattern = 'j7pairs';
                                fan += 2;
                            }
                            else{
                                fan += 2;
                            }
                        }
                    }
                    else if(sd.pattern == 'duidui'){
                        isJiangDui = this.isJiangDui(sd);
                        if(isJiangDui){
                            sd.pattern = 'jiangdui';
                            fan += 2;
                        }
                    }
                }

                if(game.conf.menQing){
                    // 不是将对才检查中张
                    if(!isJiangDui){
                        sd.isZhongZhang = this.isZhongZhang(sd);
                        if(sd.isZhongZhang){
                            fan += 1;
                        }
                    }

                    sd.isMenQing = this.isMenQing(sd);
                    if(sd.isMenQing){
                        fan += 1;
                    }
                }

                fan += sd.numOfGen;
                if(sd.isGangHu){
                    fan += 1;
                }
                if(sd.isQiangGangHu){
                    fan += 1;
                }

                // 收杠钱
                let additonalScore = 0;
                for(let a = 0; a < sd.actions.length; a++){
                    const ac = sd.actions[a];
                    if(ac.type == 'fanggang'){
                        const ts = game.gameSeats[ac.targets[0]];
                        // 检查方杠的情况，如果目标没有胡牌，且没有叫牌，则不算，用于优化前端显示
                        if(isNeedChaDaJia && ts.hued == false && this.isTinged(ts) == false){
                            ac.state = 'nop';
                        }
                    }
                    else if(ac.type == 'angang' || ac.type == 'wangang' || ac.type == 'diangang'){
                        if(ac.state != 'nop'){
                            const acScore = ac.score;
                            additonalScore += ac.targets.length * acScore * baseScore;
                            // 扣掉目标方的分
                            for(let t = 0; t < ac.targets.length; t++){
                                const six = ac.targets[t];
                                game.gameSeats[six].score -= acScore * baseScore;
                            }
                        }
                    }
                    else if(ac.type == 'maozhuanyu'){
                        // 对于呼叫转移，如果对方没有叫牌，表示不行
                        if(this.isTinged(ac.owner)){
                            const ref = ac.ref;
                            const acScore = ref.score;
                            const total = ref.targets.length * acScore * baseScore;
                            additonalScore += total;
                            // 扣掉目标方的分
                            if(ref.payTimes == 0){
                                for(let t = 0; t < ref.targets.length; t++){
                                    const six = ref.targets[t];
                                    game.gameSeats[six].score -= acScore * baseScore;
                                }
                            }
                            else{
                                // 如果已经被扣过一次了，则由杠牌的这家陪
                                ac.owner.score -= total;
                            }
                            ref.payTimes++;
                            ac.owner = null;
                            ac.ref = null;
                        }
                    }
                    else if (ac.type == 'zimo' || ac.type == 'hu' || ac.type == 'ganghua' || ac.type == 'dianganghua' || ac.type == 'gangpaohu' || ac.type == 'qiangghanghu' || ac.type == 'chadajiao'){
                        let extraScore = 0;
                        if(ac.isZiMo){
                            if(game.conf.ziMo == 0){
                                // 自摸加底
                                extraScore = baseScore;
                            }
                            if(game.conf.ziMo == 1){
                                fan += 1;
                            }
                            else{

                            }
                            sd.numZiMo++;
                        }
                        else{
                            if(ac.type != 'chadajiao'){
                                sd.numJiePao++;
                            }
                        }

                        const score = this.computeFanScore(game, fan) + extraScore;
                        sd.score += score * ac.targets.length;

                        for(let t = 0; t < ac.targets.length; t++){
                            const six = ac.targets[t];
                            const td = game.gameSeats[six];
                            td.score -= score;
                            if(td != sd){
                                if(ac.type == 'chadajiao'){
                                    td.numChaJiao++;
                                }
                                else if(!ac.isZiMo){
                                    td.numDianPao++;
                                }
                            }
                        }
                    }
                }

                if(fan > game.conf.maxFan){
                    fan = game.conf.maxFan;
                }

                sd.score += additonalScore;
                if(sd.pattern != null){
                    sd.fan = fan;
                }
            }
            else{
                for(let a = sd.actions.length - 1; a >= 0; a--){
                    const ac = sd.actions[a];
                    if(ac.type == 'angang' || ac.type == 'wangang' || ac.type == 'wangang'){
                        // 如果3家都胡牌，则需要结算。否则认为是查叫
                        if(numOfHued < 3){
                            sd.actions.splice(a, 1);
                        }
                        else{
                            if(ac.state != 'nop'){
                                const acScore = ac.score;
                                sd.score += ac.targets.length * acScore * baseScore;
                                // 扣掉目标方的分
                                for(let t = 0; t < ac.targets.length; t++){
                                    const six = ac.targets[t];
                                    game.gameSeats[six].score -= acScore * baseScore;
                                }
                            }
                        }
                    }
                }
            }
        }
    }


    /**
     * 保存历史记录，最多保存10条，多余的从头上剔除
     * @param userId 
     * @param history 
     */
    static async store_single_history(userId, history: history){
        let data = await Db.get_user_history(userId);
        if(!data){
            data = [];
        }
        while(data.length >= 10){
            data.shift();
        }
        data.push(history);
        Db.update_user_history(userId, data);
    }



    static store_history(roomInfo: roomInfo){
        const seats = roomInfo.seats;
        const history: history = {
            uuid: roomInfo.uuid,
            id: roomInfo.id,
            time: roomInfo.createTime,
            seats: [],
        };

        for(let i = 0; i < seats.length; i++){
            const rs = seats[i];
            const hs: seat = history.seats[i] = {};
            hs.userId = rs.userId,
            hs.name = Crypto.toBase64(rs.name);
            hs.score = rs.score;
        }

        for(let i = 0; i < seats.length; i++){
            const s = seats[i];
            this.store_single_history(s.userId, history);
        }
    }




    static fnNoticeResult(isEnd, roomInfo: roomInfo, results, userId, roomId){
        let endInfo: seat[] = null;
        if(isEnd){
            endInfo = [];
            for(let i = 0; i < roomInfo.seats.length; i++){
                const rs = roomInfo.seats[i];
                endInfo.push({
                    numZiMo: rs.numZiMo,
                    numJiePao: rs.numJiePao,
                    numDianPao: rs.numDianPao,
                    numAnGang: rs.numAnGang,
                    numMingGang: rs.numMingGang,
                    numChaDaJiao: rs.numChaJiao,
                });
            }
        }

        UserMgr.broacastInRoom(game_event_map.GAME_OVER_PUSH, {results: results, endInfo: endInfo}, userId, true);

        // 如果局数已够，则进行整体结算，并关闭房间
        if(isEnd){
            setTimeout(() => {
                if(roomInfo.numOfGames > 1){
                    this.store_history(roomInfo);
                }
                UserMgr.kickAllInRoom(roomId);
                Roommgr.destroy(roomId);
                Db.archive_games(roomInfo.uuid);
            }, 1500);
        }
    } 
   



    static async store_game(game: game){
        const log = logErr('store_game');

        const data = await Db.create_game(game.roomInfo.uuid, game.gameIndex, game.baseInfoJson);
        return data;
    }



    static async doGameOver(game: game, userId, forceEnd = false){
        const log = logErr('doGameOver');

        const roomId = Roommgr.getUserRoom(userId);
        if(roomId == null){
            log('roomId == null');
            return;
        }

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if(roomInfo == null){
            log('roomInfo == null');
            return;
        }

        const results = [];
        const dbResult = [0, 0, 0, 0];

        if(game != null){
            if(!forceEnd){
                this.calculateResult(game, roomInfo);
            }

            for(let i = 0; i < roomInfo.seats.length; i++){
                const rs = roomInfo.seats[i];
                const sd = game.gameSeats[i];

                rs.ready = false;
                rs.score += sd.score;
                rs.numZiMo += sd.numZiMo;
                rs.numJiePao += sd.numJiePao;
                rs.numDianPao += sd.numDianPao;
                rs.numAnGang += sd.numAnGang;
                rs.numMingGang += sd.numMingGang;
                rs.numChaJiao += sd.numChaJiao;

                const userRT: gameSeat = {
                    userId: sd.userId,
                    pengs: sd.pengs,
                    actions: [],
                    wanGangs: sd.wanGangs,
                    dianGangs: sd.dianGangs,
                    anGangs: sd.anGangs,
                    numOfGen: sd.numOfGen,
                    holds: sd.holds,
                    fan: sd.fan,
                    score: sd.score,
                    totalScore: rs.score,
                    qingYiSe: sd.qingYiSe,
                    pattern: sd.pattern,
                    isGangHu: sd.isGangHu,
                    menQing: sd.isMenQing,
                    zhongZhang: sd.isZhongZhang,
                    jinGouHu: sd.isJinGouHu,
                    haiDiHu: sd.isHaiDiHu,
                    tianHu: sd.isTianHu,
                    diHu: sd.isDiHu,
                    huOrder: game.huPaiList.indexOf(i),
                };

                for(let k in sd.actions){
                    userRT.actions[k] = {
                        type: sd.actions[k].type,
                    };
                }

                results.push(userRT);

                dbResult[i] = sd.score;
                delete this.gameSeatsOfUsers[sd.userId];
            }
            delete this.games[roomId];

            const old = roomInfo.nextButton;
            if(game.yiPaoDuoXiang >= 0){
                roomInfo.nextButton = game.yiPaoDuoXiang;
            }
            else if(game.firstHuPai >= 0){
                roomInfo.nextButton = game.firstHuPai;
            }
            else{
                roomInfo.nextButton = (game.turn + 1) % 4;
            }

            if(old != roomInfo.nextButton){
                await Db.update_next_button(roomId, roomInfo.nextButton);
            }
        }

        if(forceEnd || game == null){
            this.fnNoticeResult(true, roomInfo, results, userId, roomId);
        }
        else{
            // 保存游戏
            let ret = await this.store_game(game);
            if(ret == null){
                log('创建游戏数据失败');
                return;
            }

            ret = await Db.update_game_result(roomInfo.uuid, game.gameIndex,dbResult);
            if (ret == null) {
                log('更新result失败');
                return;
            }

            // 记录打牌信息
            const str = JSON.stringify(game.actionList);
            ret = await Db.update_game_action_records(roomInfo.uuid, game.gameIndex, str);
            if(ret == null){
                log('记录打牌信息失败');
                return;
            }

            // 保存游戏局数
            ret = await Db.update_num_of_turns(roomId, roomInfo.numOfGames);
            if (ret == null) {
                log('保存游戏局数失败');
                return;
            }

            // 如果是第一次，并且不是强制解散则扣除房卡
            if(roomInfo.numOfGames == 1){
                let cost = 2;
                if(roomInfo.conf.maxGames == 8){
                    cost = 3;
                }
                ret = await Db.cost_gems(game.gameSeats[0].userId, cost);
                if (ret == null) {
                    log('扣除房卡失败');
                    return;
                }
            }

            let isEnd = roomInfo.numOfGames >= roomInfo.conf.maxGames;
            this.fnNoticeResult(isEnd, roomInfo, results, userId, roomId)
        }
    }

    /**
     * 检查是否可以弯杠(自己摸起来的时候)
     */

    static checkCanWanGang(game: game, seatData: gameSeat){
        const log = logErr('checkCanWanGang');

        // 如果没有牌了则不能杠
        if(game.mahjongs.length <= game.currentIndex){
            log('如果没有牌了则不能杠');
            return;
        }

        // 从碰过的牌中选择
        for(let i = 0; i < seatData.pengs.length; i++){
            const pai = seatData.pengs[i];
            if(seatData.countMap[pai] == 1){
                seatData.canGang = true;
                seatData.gangPai.push(pai);
            }
        }
    }

    static doUserMoPai(game: game){
        game.chuPai = -1;
        const turnSeat = game.gameSeats[game.turn];
        turnSeat.lastFangGangSeat = -1;
        turnSeat.guoHuFan = -1;
        const pai = this.moPai(game, game.turn);
        
        if(pai == -1){
            this.doGameOver(game, turnSeat.userId);
            console.log('doUserMoPai()->pai == -1');
            return;
        }
        else{
            const numOfMJ = game.mahjongs.length - game.currentIndex;
            UserMgr.broacastInRoom(game_event_map.MJ_COUNT_PUSH, numOfMJ, turnSeat.userId, true);
        }

        this.recordGameAction(game, game.turn, ACTION.MOPAI, pai);

        // 通知前端新摸的牌
        UserMgr.sendMsg(turnSeat.userId, game_event_map.GAME_MOPAI_PUSH, pai);
        // 检查杠牌
        this.checkCanAnGang(game, turnSeat);
        this.checkCanWanGang(game, turnSeat);
        // 检查是否可以胡
        this.checkCanHu(game, turnSeat, pai);

        // 广播通知玩家出牌方
        turnSeat.canChuPai = true;
        UserMgr.broacastInRoom(game_event_map.GAME_CHUPAI_PUSH, turnSeat.userId, turnSeat.userId, true);

        // 通知玩家做对应的操作
        this.sendOperations(game, turnSeat, game.chuPai);
    }

    static chuPai(userId, pai){
        const log = logErr('chuPai');

        pai = parseInt(pai);
        const seatData = this.gameSeatsOfUsers[userId];
        console.log(`玩家${seatData.userId}出的牌是：${debugToText(pai)}`);


        if(seatData == null){
            log('seatData == null');
            return;
        }
        
        const game = seatData.game;
        const seatIndex = seatData.seatIndex;
        if(game.turn != seatData.seatIndex){
            log('没到他出牌的回合，game.turn != seatData.seatIndex');
            return;
        }

        if(seatData.hued){
            log('您已经胡牌了， 不能再出牌了');
            return;
        }

        if(seatData.canChuPai == false){
            log('seatData.canChuPai == false');
            return;
        }

        if(this.hasOperations(seatData)){
            log('执行完特殊操作才能出牌');
            return;
        }

        // 从此人牌中扣除
        const index = seatData.holds.indexOf(pai);
        if(index == -1){
            log(`在您的牌中${debugToTextArr(seatData.holds)}没有找到麻将的ID：${debugToText(pai)}`);
            return;
        }
        seatData.canChuPai = false;
        game.chuPaiCnt++;
        seatData.guoHuFan = -1;
        
        seatData.holds.splice(index, 1);
        console.log('我的手牌是：' + debugToTextArr(seatData.holds));
        seatData.countMap[pai]--;
        game.chuPai = pai;
        this.recordGameAction(game, seatData.seatIndex, ACTION.CHUPAI, pai);
        this.checkCanTingPai(game, seatData);
        //console.log('00000000000000');
        UserMgr.broacastInRoom(game_event_map.GAME_CHUPAI_NOTIFY_PUSH, {userId: seatData.userId, pai: pai}, seatData.userId, true);

        // 如果出的牌可以胡，则算过胡
        if(seatData.tingMap[game.chuPai]){
            seatData.guoHuFan = seatData.tingMap[game.chuPai].fan;
        }

        // 检查是否有人要胡， 要杠， 要碰
        let hasActions = false;
        for(let i = 0; i < game.gameSeats.length; i++){
            // 自己不检查
            if(game.turn == i){
                continue;
            }

            const ddd = game.gameSeats[i];
            // 已经胡牌的不检查
            if(ddd.hued){
                continue;
            }

            this.checkCanHu(game, ddd, pai);
            if(seatData.lastFangGangSeat == -1){
                if(ddd.canHu && ddd.guoHuFan >= 0 && ddd.tingMap[pai].fan <= ddd.guoHuFan){
                    ddd.canHu = false;
                    UserMgr.sendMsg(ddd.userId, game_event_map.GUOHU_PUSH);
                }
            }

            this.checkCanPeng(game, ddd, pai);
            this.checkCanDianGang(game, ddd, pai);
            if(this.hasOperations(ddd)){
                if(ddd.canHu){
                    //debugger;
                }
                this.sendOperations(game, ddd, game.chuPai);
                hasActions = true;
            }
        }

        // 如果没有人有操作，则向下一家发牌，并通知他出牌
        if(!hasActions){
            setTimeout(() => {
                console.log('11111111');
                UserMgr.broacastInRoom(game_event_map.GUO_NOTIFY_PUSH, {userId: seatData.userId, pai: game.chuPai}, seatData.userId, true);
                seatData.folds.push(game.chuPai);
                game.chuPai = -1;
                this.moveToNextUser(game);
                this.doUserMoPai(game);
            }, 500);
        }
    }

    /**
     * 记录玩家状态
     * @param game 
     * @param si 
     * @param action 
     * @param pai 
     */
    static recordGameAction(game: game, si, action, pai?){
        game.actionList.push(si);
        game.actionList.push(action);
        if(pai != null){
            game.actionList.push(pai);
        }
    }



    static clearAllOptions(game: game, seatData?: gameSeat){
        const fnClear = (sd: gameSeat) => {
            sd.canPeng = false;
            sd.canGang = false;
            sd.canHu = false;
            sd.gangPai = [];
            sd.lastFangGangSeat = -1;
        };

        if(seatData){
            fnClear(seatData);
        }
        else{
            game.qiangGangContext = null;
            for(let i = 0; i < game.gameSeats.length; i++){
                fnClear(game.gameSeats[i]);
            }
        }
    }


    static peng(userId){
        const log = logErr('peng');

        const seatData = this.gameSeatsOfUsers[userId];
        if(seatData == null){
            log('seatData == null');
            return;
        }

        const game = seatData.game;
        // 如果是他出的牌则忽略
        if(game.turn == seatData.seatIndex){
            log('自己的回合不能碰牌');
            return;
        }

        // 如果没有碰的机会，则不能再碰
        if(seatData.canPeng == false){
            log('seatData.canPeng == false');
            return;
        }

        // 胡了的略过
        if(seatData.hued){
            log('已经胡了，不能碰牌了');
            return;
        }

        // 如果有人可胡牌，则需要等待
        let i = game.turn;
        while(1){
            i = (i + 1) % 4;
            if(i == game.turn){
                break;
            }
            else{
                const ddd = game.gameSeats[i];
                if(ddd.canHu && i != seatData.seatIndex){
                    log('ddd.canHu && i != seatData.seatIndex');
                    return;
                }
            }
        }

        this.clearAllOptions(game);

        // 验证手上牌的数目
        const pai = game.chuPai;
        const c = seatData.countMap[pai];
        if(c == null || c < 2){
            log('c == null || c < 2');
            return;
        }

        // 进行碰牌处理，扣掉手上的牌
        for(let i = 0; i < 2; i++){
            const index = seatData.holds.indexOf(pai);
            if(index == -1){
                log('没有找到碰的牌');
                return;
            }
            seatData.holds.splice(index, 1);
            seatData.countMap[pai]--;
        }
        seatData.pengs.push(pai);
        game.chuPai--;

        this.recordGameAction(game, seatData.seatIndex, ACTION.PENG, pai);

        // 广播通知其他玩家
        UserMgr.broacastInRoom(game_event_map.PENG_NOTIFY_PUSH, {userId: seatData.userId, pai: pai}, seatData.userId, true);

        // 碰的玩家打牌
        this.moveToNextUser(game, seatData.seatIndex);

        // 广播通知玩家出牌方
        seatData.canChuPai = true;
        UserMgr.broacastInRoom(game_event_map.GAME_CHUPAI_PUSH, seatData.userId, seatData.userId, true);
    }


    static checkCanQiangGang(game: game, turnSeat: gameSeat, seatData: gameSeat, pai){
        let hasActions = false;
        for(let i = 0; i < game.gameSeats.length; i++){
            // 杠牌这不检查
            if(seatData.seatIndex == i){
                continue;
            }

            const ddd = game.gameSeats[i];
            // 已经胡牌的不检查
            if(ddd.hued){
                continue;
            }

            this.checkCanHu(game, ddd, pai);
            if(ddd.canHu){
                this.sendOperations(game, ddd, pai);
                hasActions = true;
            }
        }

        if(hasActions){
            game.qiangGangContext = {
                turnSeat: turnSeat,
                seatData: seatData,
                pai: pai,
                isValid: true,
            };
        }
        else{
            game.qiangGangContext = null;
        }
        return game.qiangGangContext != null;
    }




    


    static doGang(game: game, turnSeat: gameSeat, seatData: gameSeat, gangType, numOfCnt, pai){
        const log = logErr('doGang');
        
        const seatIndex = seatData.seatIndex;
        const gameTurn = turnSeat.seatIndex;

        let isZhuanShouGang = false;
        if(gangType == 'wangang'){
            const idx = seatData.pengs.indexOf(pai);
            if(idx >= 0){
                seatData.pengs.splice(idx, 1)
            }

            // 如果最后一张牌不是杠的牌，则认为是转手杠
            if(seatData.holds[seatData.holds.length - 1] != pai){
                isZhuanShouGang = true;
            }
        }

        // 杠牌处理，从此人手中扣除牌
        for(let i = 0; i < numOfCnt; i++){
            const index = seatData.holds.indexOf(pai);
            if(index == -1){
                log('没有找到牌');
                return;
            }
            seatData.holds.splice(index, 1);
            seatData.countMap[pai]--;
        }

        this.recordGameAction(game, seatData.seatIndex, ACTION.GANG, pai);

        // 记录下玩家的杠牌
        if(gangType == 'angang'){
            seatData.anGangs.push(pai);
            const ac = this.recordUserAction(game, seatData, 'angang');
            ac.score = game.conf.baseScore * 2;
        }
        else if(gangType == 'diangang'){
            seatData.dianGangs.push(pai);
            const ac = this.recordUserAction(game, seatData, 'diangang', gameTurn);
            ac.score = game.conf.baseScore * 2;
            this.recordUserAction(game, turnSeat, 'fanggang', seatIndex);
        }
        else if(gangType == 'wangang'){
            seatData.wanGangs.push(pai);
            if(isZhuanShouGang == false){
                const ac = this.recordUserAction(game, seatData, 'wangang');
                ac.score = game.conf.baseScore;
            }
            else{
                this.recordUserAction(game, seatData, 'zhuanshougang');
            }
        }

        this.checkCanTingPai(game, seatData);

        // 通知其他玩家有人杠牌了
        UserMgr.broacastInRoom(game_event_map.GANG_NOTIFY_PUSH, {userId: seatData.userId, pai: pai, gangType: gangType}, seatData.userId, true);

        this.moveToNextUser(game, seatIndex);
        // 再次摸牌
        this.doUserMoPai(game);

        seatData.lastFangGangSeat = gameTurn;
    }



    static gang(userId, pai){
        const log = logErr('gang');

        const seatData = this.gameSeatsOfUsers[userId];
        if(seatData == null){
            log('seatData == null');
            return;
        }

        const seatIndex = seatData.seatIndex;
        const game = seatData.game;

        // 如果没有杠的机会，则不能再杠
        if(seatData.canGang == false){
            log('seatData.canGang == false');
            return;
        }

        // 胡牌的略过
        if(seatData.hued){
            log('胡牌的略过');
            return;
        }

        if(seatData.gangPai.indexOf(pai) == -1){
            log('seatData.gangPai.indexOf(pai) == -1');
            return;
        }

        // 如果有人可以胡牌则等待
        let i = game.turn;
        while(1){
            i = (i + 1) % 4;
            if(i == game.turn){
                break;
            }
            else{
                const ddd = game.gameSeats[i];
                if(ddd.canHu && i != seatData.seatIndex){
                    log('ddd.canHu && i != seatData.seatIndex');
                    return;
                }
            }
        }

        const numOfCnt = seatData.countMap[pai];
        let gangType = '';
        if(numOfCnt == 1){
            gangType = 'wangang';
        }
        else if(numOfCnt == 3){
            gangType = 'diangang';
        }
        else if(numOfCnt == 4){
            gangType = 'angang';
        }
        else{
            log('无效的杠牌数量');
            return;
        }

        game.chuPai = -1;
        this.clearAllOptions(game);
        seatData.canChuPai = false;

        UserMgr.broacastInRoom(game_event_map.HANGANG_NOTIFY_PUSH, seatIndex, seatData.userId, true);

        // 如果是弯杠，则需要检查是否可抢杠
        const turnSeat = game.gameSeats[game.turn];
        if(numOfCnt == 1){
            const canQiangGang = this.checkCanQiangGang(game, turnSeat, seatData, pai);
            if(canQiangGang){
                log('canQiangGang == true');
                return;
            }
        }

        this.doGang(game, turnSeat, seatData, gangType, numOfCnt, pai);
    }



    static hu(userId){
        const log = logErr('hu');

        const seatData = this.gameSeatsOfUsers[userId];
        if(seatData == null){
            log('seatData == null');
            return;
        }

        const seatIndex = seatData.seatIndex;
        const game = seatData.game;

        if(seatData.canHu == false){
            log('seatData.canHu == false');
            return;
        }

        if(seatData.hued){
            log('已经胡过了');
            return;
        }

        seatData.hued = true;
        let huPai = game.chuPai;
        let isZiMo = false;

        const turnSeat = game.gameSeats[game.turn];
        seatData.isGangHu = turnSeat.lastFangGangSeat >= 0;
        let notify = -1;

        if(game.qiangGangContext != null){
            const gangSeat = game.qiangGangContext.seatData;
            huPai = game.qiangGangContext.pai;
            notify = huPai;
            const ac = this.recordUserAction(game, seatData, 'qiangganghu', gangSeat.seatIndex);
            ac.isZiMo = false;
            this.recordGameAction(game, seatIndex, ACTION.HU, huPai);
            seatData.isQiangGangHu = true;
            game.qiangGangContext.isValid = false;

            const idx = gangSeat.holds.indexOf(huPai);
            if(idx != -1){
                gangSeat.holds.splice(idx, 1);
                gangSeat.countMap[huPai]--;
                UserMgr.sendMsg(gangSeat.userId, game_event_map.GAME_HOLDS_PUSH, gangSeat.holds);
            }

            // 将牌添加到玩家的手牌列表，供前端显示
            seatData.holds.push(huPai);
            if(seatData.countMap[huPai]){
                seatData.countMap[huPai]++;
            }
            else{
                seatData.countMap[huPai] = 1;
            }

            this.recordUserAction(game, gangSeat, 'beiqianggang', seatIndex);
        }
        else if(game.chuPai == -1){
            huPai = seatData.holds[seatData.holds.length - 1];
            notify = -1;
            if(seatData.isGangHu){
                if(turnSeat.lastFangGangSeat == seatIndex){
                    const ac = this.recordUserAction(game, seatData, 'ganghua');
                    ac.isZiMo = true;
                }
                else {
                    const dianGangHuaZiMo = game.conf.dianGangHua == 1;
                    if (dianGangHuaZiMo) {
                        const ac = this.recordUserAction(game, seatData, 'dianganghua');
                        ac.isZiMo = true;
                    }
                    else {
                        const ac = this.recordUserAction(game, seatData, 'dianganghua', turnSeat.lastFangGangSeat);
                        ac.isZiMo = false;
                    }
                }
            }
            else {
                const ac = this.recordUserAction(game, seatData, 'zimo');
                ac.isZiMo = false;
            }
            
            isZiMo = true;
            this.recordGameAction(game, seatIndex, ACTION.ZIMO, huPai);
        }
        else{
            notify = game.chuPai;
            // 将牌添加到玩家的手牌列表，供前端显示
            seatData.holds.push(game.chuPai);
            if(seatData.countMap[game.chuPai]){
                seatData.countMap[game.chuPai]++;
            }
            else{
                seatData.countMap[game.chuPai] = 1;
            }

            let at = 'hu';
            // 炮胡
            if(turnSeat.lastFangGangSeat >= 0){
                at = 'gangpaohu';
            }

            const ac = this.recordUserAction(game, seatData, at, game.turn);
            ac.isZiMo = false;

            // 毛转雨
            if(turnSeat.lastFangGangSeat >= 0){
                for(let i = turnSeat.actions.length - 1; i >= 0; i--){
                    const t = turnSeat.actions[i];
                    if(t.type == 'diangang' || t.type == 'wangang' || t.type == 'angang'){
                        t.state = 'nop';
                        t.payTimes = 0;
                        const nac: actions = {
                            type: 'maozhuanyu',
                            owner: turnSeat,
                            ref: t,
                        };
                        seatData.actions.push(nac);
                        break;
                    }
                }
            }

            // 记录玩家放炮信息
            const fs = game.gameSeats[game.turn];
            this.recordUserAction(game, fs, 'fangpao', seatIndex);
            this.recordGameAction(game, seatIndex, ACTION.HU, huPai);

            game.fangPaoShuMu++;
            if(game.fangPaoShuMu >1){
                game.yiPaoDuoXiang = seatIndex;
            }
        }

        if(game.firstHuPai < 0){
            game.firstHuPai = seatIndex;
        }

        // 保存番数
        const ti = seatData.tingMap[huPai];
        seatData.fan = ti.fan;
        seatData.pattern = ti.pattern;
        seatData.isZiMo = isZiMo;
        // 如果是最后一张牌，则认为是海底胡
        seatData.isHaiDiHu = game.currentIndex == game.mahjongs.length;
        game.huPaiList.push(seatData.seatIndex);

        if(game.conf.tianDiHu){
            if(game.chuPaiCnt == 0 && game.button == seatData.seatIndex && game.chuPai == -1){
                seatData.isTianHu = true;
            }
            else if(game.chuPaiCnt == 1 && game.turn == game.button && game.button != seatData.seatIndex && game.chuPai != -1){
                seatData.isDiHu = true;
            }
        }

        this.clearAllOptions(game, seatData);

        // 通知前端有人胡牌了
        UserMgr.broacastInRoom(game_event_map.HU_PUSH, {seatIndex: seatIndex, isZiMo: isZiMo, huPai: notify}, seatData.userId, true);

        if(game.lastHuPaiSeat == -1){
            game.lastHuPaiSeat = seatIndex;
        }
        else{
            const lp = (game.lastHuPaiSeat - game.turn + 4) % 4;
            const cur = (seatData.seatIndex - game.turn + 4) % 4;
            if(cur > lp){
                game.lastHuPaiSeat = seatData.seatIndex;
            }
        }

        // 如果只有一家没有胡则结束
        let numOfHued = 0;
        game.gameSeats.forEach((ddd) => {
            if(ddd.hued){
                numOfHued++;
            }
        });

        if(numOfHued == 3){
            this.doGameOver(game, seatData.userId);
            log('numOfHued == 3');
            return;
        }

        // 清空所有非胡牌的操作
        game.gameSeats.forEach((ddd) => {
            ddd.canPeng = false;
            ddd.canGang = false;
            ddd.canChuPai = false;
            this.sendOperations(game, ddd, huPai);
        });

        // 如果还有人可以胡牌，则等待
        for(let i = 0; i < game.gameSeats.length; i++){
            let ddd = game.gameSeats[i];
            if(ddd.canHu){
                log('ddd.canHu == true');
                return;
            }

        }

        // 胡牌的下家继续打
        this.clearAllOptions(game);
        game.turn = game.lastHuPaiSeat;
        this.moveToNextUser(game);
        this.doUserMoPai(game);
    }




    static guo(userId){
        const log = logErr('guo');

        const seatData = this.gameSeatsOfUsers[userId];
        if(seatData == null){
            log('seatData == null');
            return;
        }

        const seatIndex = seatData.seatIndex;
        const game = seatData.game;

        if(seatData.canGang == false && seatData.canPeng == false && seatData.canHu == false){
            log('没有对应的响应，你过的什么啊');
            return;
        }

        const doNothing = game.chuPai == -1 && game.turn == seatIndex;

        UserMgr.sendMsg(seatData.userId, game_event_map.GUO_RESULT);
        this.clearAllOptions(game, seatData);

        // 这里还要处理过胡的情况
        if(game.chuPai >= 0 && seatData.canHu){
            seatData.guoHuFan = seatData.tingMap[game.chuPai].fan;
        }

        if(doNothing){
            log('doNothing == true');
            return;
        }

        // 如果还有人可以操作则等待
        for(let i = 0; i < game.gameSeats.length; i++){
            let ddd = game.gameSeats[i];
            if(this.hasOperations(ddd)){
                log('如果还有人可以操作则等待');
                return;
            }

        }


        // 如果是已打出的牌，则需要通知
        if(game.chuPai >= 0){
            const uid = game.gameSeats[game.turn].userId;
            UserMgr.broacastInRoom(game_event_map.GUO_NOTIFY_PUSH, {userId: uid, pai: game.chuPai}, seatData.userId, true);
            seatData.folds.push(game.chuPai);
            game.chuPai = -1;
        }

        const qiangGangContext = game.qiangGangContext;
        // 清楚所有操作
        this.clearAllOptions(game);

        if(qiangGangContext && qiangGangContext.isValid){
            this.doGang(game, qiangGangContext.turnSeat, qiangGangContext.seatData, 'wangang', 1, qiangGangContext.pai);
        }
        else{
            this.moveToNextUser(game);
            this.doUserMoPai(game);
        }
    }

    /**
     * 检查房间是否已经开始游戏
     * @param roomId 
     */
    static hasBegan(roomId){
        const game = this.games[roomId];
        if(game != null){
            return true;
        }

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if(roomInfo != null){
            return roomInfo.numOfGames > 0;
        }
        return false;
    }


    static dissolveRequest(roomId, userId){
        const log = logErr('dissolveRequest');

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if(roomInfo == null){
            log('roomInfo == null');
            return;
        }

        if(roomInfo.dr != null){
            log('roomInfo.dr != null');
            return;
        }

        const seatIndex = Roommgr.getUserSeat(userId);
        if(seatIndex == null){
            log('seatIndex == null');
            return;
        }

        roomInfo.dr = {
            endTime: Date.now() + 30000,
            states: [false, false, false, false],
        };
        roomInfo.dr.states[seatIndex] = true;
        this.dissolvingList.push(roomId);
        return roomInfo;
    }


    static doDisslove(roomId){
        const log = logErr('doDisslove');

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if(roomInfo == null){
            log('roomInfo == null');
            return null;
        }

        const game = this.games[roomId];
        this.doGameOver(game, roomInfo.seats[0].userId, true);
    }


    static dissolveAgree(roomId, userId, agree){
        const log = logErr('dissolveAgree');

        const roomInfo = Roommgr.getRoomInfo(roomId);
        if (roomInfo == null) {
            log('roomInfo == null');
            return;
        }

        if (roomInfo.dr == null) {
            log('roomInfo.dr == null');
            return;
        }

        const seatIndex = Roommgr.getUserSeat(userId);
        if (seatIndex == null) {
            log('seatIndex == null');
            return;
        }

        if(agree){
            roomInfo.dr.states[seatIndex] = true;
        }
        else{
            roomInfo.dr = null;
            const idx = this.dissolvingList.indexOf(roomId);
            if(idx != -1){
                this.dissolvingList.splice(idx, 1);
            }
        }
        return roomInfo;
    }

}